package branch_and_bound_vrptw;

import ilog.concert.IloException;
import ilog.concert.IloNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloNumVarType;
import ilog.cplex.IloCplex;

import java.util.ArrayList;

public class Model {
    Data data;
    private double x_gap;
    IloCplex mp;
    IloNumVar[][][] x;
    IloNumVar[][] w;
    double cost;                //目标值object
    double[][][] x_map;//cplex参数x
    ArrayList<ArrayList<Integer>> routes;        //定义车辆路径链表
    ArrayList<ArrayList<Double>> servetimes;    //定义花费时间链表
    // 构造器方法 防止空指针嘿嘿
    public Model(Data data) {
        this.data = data;
        x_gap = Data.GAP;
        routes = new ArrayList<>();        //定义车辆路径链表
        servetimes = new ArrayList<>();    //定义花费时间链表
        //初始化车辆路径和花费时间链表，链表长度为车辆数k
        for (int k = 0; k < data.veh_num; k++) {
            ArrayList<Integer> r = new ArrayList<>();
            ArrayList<Double> t = new ArrayList<>();
            routes.add(r);
            servetimes.add(t);
        }
        x_map = new double[data.getVertex_num()][data.getVertex_num()][data.veh_num];
    }

    //函数功能：根据VRPTW数学模型建立VRPTW的cplex模型
    //建立模型
    public void build_model() throws IloException {
        //mp
        mp = new IloCplex();
        mp.setOut(null);
//		mp.setParam(IloCplex.DoubleParam.EpOpt, 1e-9);
//		mp.setParam(IloCplex.DoubleParam.EpGap, 1e-9);
        //variables
        x = new IloNumVar[data.getVertex_num()][data.getVertex_num()][data.veh_num];
        w = new IloNumVar[data.getVertex_num()][data.veh_num];                //车辆访问点的时间
        //定义cplex变量x和w的数据类型及取值范围
        for (int i = 0; i < data.getVertex_num(); i++) {
            for (int k = 0; k < data.veh_num; k++) {
                w[i][k] = mp.numVar(0, 1e15, IloNumVarType.Float, "w" + i + "," + k);
            }
            for (int j = 0; j < data.getVertex_num(); j++) {
                if (data.arcs[i][j] == 0) {
                    x[i][j] = null;
                } else {
                    //Xijk,公式(10)-(11)
                    for (int k = 0; k < data.veh_num; k++) {
                        x[i][j][k] = mp.numVar(0, 1, IloNumVarType.Float, "x" + i + "," + j + "," + k);
                    }
                }
            }
        }
        //加入目标函数
        //公式(1)
        IloNumExpr obj = mp.numExpr();
        for (int i = 0; i < data.getVertex_num(); i++) {
            for (int j = 0; j < data.getVertex_num(); j++) {
                if (data.arcs[i][j] == 0) {
                    continue;
                }
                for (int k = 0; k < data.veh_num; k++) {
                    obj = mp.sum(obj, mp.prod(data.dist[i][j], x[i][j][k]));
                }
            }
        }
        mp.addMinimize(obj);
        //加入约束
        //公式(2)
        for (int i = 1; i < data.getVertex_num() - 1; i++) {
            IloNumExpr expr1 = mp.numExpr();
            for (int k = 0; k < data.veh_num; k++) {
                for (int j = 1; j < data.getVertex_num(); j++) {
                    if (data.arcs[i][j] == 1) {
                        expr1 = mp.sum(expr1, x[i][j][k]);
                    }
                }
            }
            mp.addEq(expr1, 1);
        }
        //公式(3)
        for (int k = 0; k < data.veh_num; k++) {
            IloNumExpr expr2 = mp.numExpr();
            for (int j = 1; j < data.getVertex_num(); j++) {
                if (data.arcs[0][j] == 1) {
                    expr2 = mp.sum(expr2, x[0][j][k]);
                }
            }
            mp.addEq(expr2, 1);
        }
        //公式(4)
        for (int k = 0; k < data.veh_num; k++) {
            for (int j = 1; j < data.getVertex_num() - 1; j++) {
                IloNumExpr expr3 = mp.numExpr();
                IloNumExpr subExpr1 = mp.numExpr();
                IloNumExpr subExpr2 = mp.numExpr();
                for (int i = 0; i < data.getVertex_num(); i++) {
                    if (data.arcs[i][j] == 1) {
                        subExpr1 = mp.sum(subExpr1, x[i][j][k]);
                    }
                    if (data.arcs[j][i] == 1) {
                        subExpr2 = mp.sum(subExpr2, x[j][i][k]);
                    }
                }
                expr3 = mp.sum(subExpr1, mp.prod(-1, subExpr2));
                mp.addEq(expr3, 0);
            }
        }
        //公式(5)
        for (int k = 0; k < data.veh_num; k++) {
            IloNumExpr expr4 = mp.numExpr();
            for (int i = 0; i < data.getVertex_num() - 1; i++) {
                if (data.arcs[i][data.getVertex_num() - 1] == 1) {
                    expr4 = mp.sum(expr4, x[i][data.getVertex_num() - 1][k]);
                }
            }
            mp.addEq(expr4, 1);
        }
        //公式(6)
        double M = 1e5;
        for (int k = 0; k < data.veh_num; k++) {
            for (int i = 0; i < data.getVertex_num(); i++) {
                for (int j = 0; j < data.getVertex_num(); j++) {
                    if (data.arcs[i][j] == 1) {
                        IloNumExpr expr5 = mp.numExpr();
                        IloNumExpr expr6 = mp.numExpr();
                        expr5 = mp.sum(w[i][k], data.s[i] + data.dist[i][j]);
                        expr5 = mp.sum(expr5, mp.prod(-1, w[j][k]));
                        expr6 = mp.prod(M, mp.sum(1, mp.prod(-1, x[i][j][k])));
                        mp.addLe(expr5, expr6);
                    }
                }
            }
        }
        //公式(7)
        for (int k = 0; k < data.veh_num; k++) {
            for (int i = 1; i < data.getVertex_num() - 1; i++) {
                IloNumExpr expr7 = mp.numExpr();
                for (int j = 0; j < data.getVertex_num(); j++) {
                    if (data.arcs[i][j] == 1) {
                        expr7 = mp.sum(expr7, x[i][j][k]);
                    }
                }
                mp.addLe(mp.prod(data.a[i], expr7), w[i][k]);
                mp.addLe(w[i][k], mp.prod(data.b[i], expr7));
            }
        }
        //公式(8)
        for (int k = 0; k < data.veh_num; k++) {
            mp.addLe(data.E, w[0][k]);
            mp.addLe(data.E, w[data.getVertex_num() - 1][k]);
            mp.addLe(w[0][k], data.L);
            mp.addLe(w[data.getVertex_num() - 1][k], data.L);
        }
        //公式(9)
        for (int k = 0; k < data.veh_num; k++) {
            IloNumExpr expr8 = mp.numExpr();
            for (int i = 1; i < data.getVertex_num() - 1; i++) {
                IloNumExpr expr9 = mp.numExpr();
                for (int j = 0; j < data.getVertex_num(); j++) {
                    if (data.arcs[i][j] == 1) {
                        expr9 = mp.sum(expr9, x[i][j][k]);
                    }
                }
                expr8 = mp.sum(expr8, mp.prod(data.demands[i], expr9));
            }
            mp.addLe(expr8, data.cap);
        }
    }

    //函数功能：解模型，并生成车辆路径和得到目标值
    //获取cplex解
    public void get_value() throws IloException {
        routes.clear();
        servetimes.clear();
        cost = 0;
//		//初始化车辆路径和花费时间链表，链表长度为车辆数k
        for (int k = 0; k < data.veh_num; k++) {
            ArrayList<Integer> r = new ArrayList<>();
            ArrayList<Double> t = new ArrayList<>();
            routes.add(r);
            servetimes.add(t);
        }
        for (int i = 0; i < data.getVertex_num(); i++) {
            for (int j = 0; j < data.getVertex_num(); j++) {
                for (int k = 0; k < data.veh_num; k++) {
                    x_map[i][j][k] = 0.0;
                }
                if (data.arcs[i][j] > 0.5) {
                    for (int k = 0; k < data.veh_num; k++) {
                        x_map[i][j][k] = mp.getValue(x[i][j][k]);
                    }
                }
            }
        }
        //模型可解，生成车辆路径
        for (int k = 0; k < data.veh_num; k++) {
            boolean terminate = true;
            int i = 0;
            routes.get(k).add(0);
            servetimes.get(k).add(0.0);
            while (terminate) {
                for (int j = 0; j < data.getVertex_num(); j++) {
                    if (doubleCompare(x_map[i][j][k], 0) == 1) {
                        routes.get(k).add(j);
                        servetimes.get(k).add(mp.getValue(w[j][k]));
                        i = j;
                        break;
                    }
                }
                if (i == data.getVertex_num() - 1) {
                    terminate = false;
                }
            }
        }
        cost = mp.getObjValue();
    }
    //比较两个double数值的大小
    public int doubleCompare(double a, double b) {
        if (a - b > x_gap)
            return 1;
        if (b - a > x_gap)
            return -1;
        return 0;
    }
}